﻿<?php
error_reporting(E_ERROR | E_PARSE);

class Hoop_Memcache extends Memcache {

	private function _makeLockKey($key) {
		return $key.'$$lock';
	}

	public function get($keys) {
	  if (!is_array($keys)) {
	    $lkey = self::_makeLockKey($keys);
	    $hmap = parent::get(array($keys, $lkey));
	    if (!isset($hmap[$lkey]) || $hmap[$lkey] === 'LOCK')
		  return FALSE;
	    return $hmap[$keys];
	  }
	  else {
	    $allkeys = $keys;
		foreach ($keys as $key)
		  $allkeys[] = self::_makeLockKey($key);
		$hmap = parent::get($allkeys);
		foreach ($keys as $key) {
		  $lkey = self::_makeLockKey($key);
		  if (!isset($hmap[$lkey]) || $hmap[$lkey] === 'LOCK')
		    $hmap[$key] = FALSE;
		  unset($hmap[$lkey]);
		}
		return $hmap;
	  }
	}

	public function getOrLock($keys) {
	  if (!is_array($keys)) {
	    $lkey = self::_makeLockKey($keys);
	    $hmap = parent::get(array($keys, $lkey));
	    if (!isset($hmap[$lkey])) {
		  parent::add($lkey, 'LOCK', 0, 15);
		  return FALSE;
		}
	    return $hmap[$keys];
	  }
	  else {
	    $allkeys = $keys;
		foreach ($keys as $key)
		  $allkeys[] = self::_makeLockKey($key);
		$hmap = parent::get($allkeys);
		foreach ($keys as $key) {
		  $lkey = self::_makeLockKey($key);
		  if (!isset($hmap[$lkey])) {
		    parent::add($lkey, 'LOCK', 0, 15);
		    $hmap[$key] = FALSE;
		  }
		  unset($hmap[$lkey]);
		}
		return $hmap;
	  }
	}

	public function set($key, $var, $flag = 0, $expire = 0) {
	  if (parent::set($key, $var, $flag, (($expire > 0) ? ($expire + 600) : 0))) {
	    if (parent::set(self::_makeLockKey($key), (($expire > 0) ? (time() + $expire) : 0), 0, $expire))
          return TRUE;
		else
          parent::delete($key);
      }
	  return FALSE;
    }
	
	public function add($key, $var, $flag = 0, $expire = 0) {
	  $lkey = self::_makeLockKey($key);
	  $lock = parent::get($lkey);
	  if ($lock !== FALSE) {
	    return FALSE;
	  }
	  return self::set($key, $var, $flag, $expire);
	}

	public function replace($key, $var, $flag = 0, $expire = 0) {
	  $lkey = self::_makeLockKey($key);
	  $lock = parent::get($lkey);
	  if ($lock === FALSE) {
	    return FALSE;
	  }
	  return self::set($key, $var, $flag, $expire);
	}

	public function delete($key, $expire = 0) {
	  $lkey = self::_makeLockKey($key);
	  $lock = parent::get($lkey);
	  if ($lock === FALSE || $lock === 'LOCK') {
	    return FALSE;
	  }
	  else if ($expire == 0) {
	    return parent::delete($lkey);
	  }
	  else {
		$etim = time() + $expire;
		if ($lock == 0 || $lock > $etim)
		  return parent::set($lkey, $etim, 0, $expire);
		return TRUE;
	  }
	}

}

$memcache = new Hoop_Memcache();
$memcache->connect('127.0.0.1', 11211);

if ($memcache) {
		$memcache->set('str_key', 'string to store in memcached', 0, 3600);
		$memcache->getOrLock('str_key', 1);

        $memcache->set("str_key", "String to store in memcached");
        $memcache->set("num_key", array(1, 2, 3));

        echo 'stage 1...'.PHP_EOL;
        var_dump($memcache->get('str_key'));
        var_dump($memcache->get('num_key'));
        var_dump($memcache->get(array('str_key', 'num_key')));

        $memcache->delete("str_key");
        echo 'stage 2...'.PHP_EOL;
        var_dump($memcache->get('str_key'));
        var_dump($memcache->get(array('str_key', 'num_key')));
        var_dump($memcache->getOrLock(array('str_key', 'num_key')));
        var_dump($memcache->getOrLock('str_key'));
        var_dump($memcache->getOrLock('str_key'));
        var_dump($memcache->get('str_key'));
        var_dump($memcache->get(array('str_key', 'num_key')));
        var_dump($memcache->getOrLock('str_key'));
        var_dump($memcache->getOrLock(array('str_key', 'num_key')));

        $memcache->delete("num_key", 2);
        echo 'stage 3...'.PHP_EOL;
        var_dump($memcache->get('num_key'));
        var_dump($memcache->getOrLock('num_key'));
        var_dump($memcache->getOrLock(array('str_key', 'num_key')));

        usleep((2 * 1100) * 1000); // 2.2s
        echo 'stage 4...'.PHP_EOL;
        var_dump($memcache->getOrLock('num_key'));
        var_dump($memcache->getOrLock('num_key'));
        var_dump($memcache->getOrLock('num_key'));
        var_dump($memcache->getOrLock(array('str_key', 'num_key')));
}
else {
        echo "Connection to memcached failed";
}
